/*
 * JSFConsole - Lightweight JSF Application Monitor
 * Copyright (C) 2009  Grzegorz Bugaj
 * http://www.gregbugaj.com
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
package com.gregbugaj.jsfconsole.console.managed;

import java.io.IOException;



import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.el.ArrayELResolver;
import javax.el.BeanELResolver;
import javax.el.CompositeELResolver;
import javax.el.ListELResolver;
import javax.el.MapELResolver;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;

import com.googlecode.charts4j.AxisLabels;
import com.googlecode.charts4j.AxisLabelsFactory;
import com.googlecode.charts4j.AxisStyle;
import com.googlecode.charts4j.AxisTextAlignment;
import com.googlecode.charts4j.Color;
import com.googlecode.charts4j.Data;
import com.googlecode.charts4j.GCharts;
import com.googlecode.charts4j.Line;
import com.googlecode.charts4j.LineChart;
import com.googlecode.charts4j.Plots;
import com.gregbugaj.jsfconsole.agent.TraceEvent;
import com.gregbugaj.jsfconsole.agent.tracker.EventInfo;
import com.gregbugaj.jsfconsole.agent.tracker.EventLogTracker;
import com.gregbugaj.jsfconsole.agent.tracker.PhaseEventInfo;
import com.gregbugaj.jsfconsole.agent.tracker.PhaseTracker;
import com.gregbugaj.jsfconsole.agent.tracker.RequestInfo;
import com.gregbugaj.jsfconsole.agent.tracker.ValidationEventInfo;
import com.gregbugaj.jsfconsole.agent.tracker.ValidationMessageTracker;
import com.gregbugaj.jsfconsole.enums.ViewType;
import com.gregbugaj.jsfconsole.enums.ScopeType;
import com.gregbugaj.jsfconsole.util.FacesUtil;
import com.gregbugaj.jsfconsole.util.Predicate;
import com.gregbugaj.jsfconsole.util.reflection.ELResolverReflectionProvider;
import com.gregbugaj.reflector.renderer.AbstractRenderer;
import com.gregbugaj.reflector.renderer.TreeRenderer;
import com.gregbugaj.reflector.visitors.JSFDumpContext;
import com.gregbugaj.reflector.visitors.XMLVisitor;
/**
 * 
 * @author devil
 *
 */
public class AjaxController {
	private PhaseTracker phaseTracker;	
	//Tracks all the events
	private EventLogTracker eventTracker;

	//Tracks Validation Messages
	private ValidationMessageTracker validationTracker;

	public AjaxController(){
		System.out.println("AjaxController.AjaxController()");
		init();
	}

	public String init(){
		System.out.println("AjaxController.init()");
		//Log Phase Events
		phaseTracker=new PhaseTracker();
		//Log Validation Events
		validationTracker= new ValidationMessageTracker();
		validationTracker.startMonitoring();

		//Log all events
		eventTracker=new EventLogTracker();
		eventTracker.setFilter(new Predicate<TraceEvent>(){
			public boolean evaluate(TraceEvent item) {
				return true;
			}
		});
		eventTracker.startMonitoring();
		return "Initalized";
	}

	public String startMonitoringLifecycle(ArrayList<String> params){
		System.out.println("Handling startMonitoringLifecycle = "+params);
		//Filtering our request so we don't polute tracking info
		phaseTracker.setFilter(new Predicate<TraceEvent>(){
			public boolean evaluate(TraceEvent event) {
				boolean retVal=true;
				FacesContext context=(FacesContext) event.getArguments()[0];
				String contextRoot=context.getExternalContext().getRequestServletPath();
				if(contextRoot.indexOf("/jsfdump/")> -1){
					retVal=false;
				}
				return retVal;
			}

		});
		phaseTracker.startMonitoring();
		return "Monitoring Started";
	}


	public String stopMonitoringLifecycle(ArrayList<String> params){
		phaseTracker.stopMonitoring();		
		return "Monitoring Ended";
	}

	public String showValidationLog(ArrayList<String> params){
		ViewType viewType=ViewType.EXPANDED;
		if(params.size()!=0){
			viewType=ViewType.fromValue(params.get(0));
		}
		
		List<RequestInfo> events=validationTracker.getEvents(100);		
		if(events == null || events.size()==0){
			return "No Validation events found.";
		}		
		RequestInfo info=events.get(0);

		
		
		StringBuilder builder=new StringBuilder();
		builder.append("<ul class='jsfconsole-validation'>");
		builder.append(info+"  "+info.getEvents().size());
		for(EventInfo event:info.getEvents()){
			ValidationEventInfo ev=(ValidationEventInfo) event;
			builder.append("<li>");
			builder.append(ev);
			builder.append("<ul>");
			int inx=0;//Show  trace of last 3 calls

			for(StackTraceElement el:ev.getStackTrace()){
				builder.append("<li>");
				builder.append(el);
				builder.append("</li>");	
				if(viewType.equals(ViewType.COMPACT) && inx > 3){
				    break;	
				}
				inx++;
			}
			
			builder.append("</ul>");
			
			builder.append("</li>");
		}
		builder.append("</ul>");
		return builder.toString();

		/*return "NOT IMPL";*/
	}

	public String showEventLogRequest(ArrayList<String> params){
	
		
		List<RequestInfo> events=eventTracker.getEvents(100);		
		if(events == null || events.size()==0){
			return "No events found.";
		}		
	
		StringBuilder builder=new StringBuilder();
		builder.append("<ul>");
		
		
		for(RequestInfo ev: events){
			builder.append("<li><a href='javascript:void(0)'>");
			builder.append(ev);
			builder.append("</a></li>");
			
		}
		builder.append("</ul>");
		return builder.toString();
	}
	
	public String showEventLog(ArrayList<String> params){
		List<RequestInfo> events=eventTracker.getEvents(100);		
		if(events == null || events.size()==0){
			return "No events found.";
		}		
		RequestInfo info=events.get(0);

		StringBuilder builder=new StringBuilder();
		builder.append("<ul>");
		builder.append("<ul>"+info+"  "+info.getEvents().size());
		for(EventInfo ev:info.getEvents()){
			builder.append("<li>");
			builder.append(ev);
			builder.append("</li>");
		}
		builder.append("</ul>");
		return builder.toString();
	}



	public String showEvents(ArrayList<String> params){
		String filter="/";
		ViewType viewType=ViewType.EXPANDED;

		if(params.size()!=0){
			filter=params.get(0);
			viewType=ViewType.fromValue(params.get(1));
		}


		List<RequestInfo> events=phaseTracker.getEvents(100, filter);
		List<String> requestTimes=new ArrayList<String>();
		for(RequestInfo info:events){
			List<String> times=new ArrayList<String>();	
			for(EventInfo ev:info.getEvents()){
				times.add(""+ev.getElapsedTime());
			}
			requestTimes.add(times.toString().replace("[", "").replace("]", "").replace(" ",""));
		}

		int index=0;
		StringBuilder builder=new StringBuilder();
		if(viewType.equals(ViewType.EXPANDED)){
			builder.append("<ul class='jsfdump-lifecycle-phase'>");
			for(RequestInfo info:events){
				String rTime=requestTimes.get(index);
				builder.append("<li>");
				builder.append("<a href='javascript:void(0)' onclick='updatePhaseGraph(\""+rTime+"\");'>");
				builder.append(info+"="+rTime);
				builder.append("</a>");
				builder.append("<ul>");
				for(EventInfo ev:info.getEvents()){
					builder.append("<li>");
					builder.append(ev);
					builder.append("</li>");
				}
				builder.append("</ul>");
				builder.append("</li>");
				index++;
			}
			builder.append("</ul>");
		}else{//Collapsed


			builder.append("<table class='jsfdump-lifecycle-phase'>");
			builder.append("<tr>");

			/*
			builder.append("<th>RESTORE_VIEW</th>");
			builder.append("<th>APPLY_REQUEST_VALUES</th>");
			builder.append("<th>PROCESS_VALIDATIONS</th>");
			builder.append("<th>UPDATE_MODEL_VALUES</th>");
			builder.append("<th>INVOKE_APPLICATION</th>");
			builder.append("<th>RENDER_RESPONSE</th>");
			 */
			builder.append("<th style='width:40%'>PATH</th>");

			builder.append("<th style='width:10%'>RESTORE</th>");
			builder.append("<th style='width:10%'>APPLY</th>");
			builder.append("<th style='width:10%'>VALIDATE</th>");
			builder.append("<th style='width:10%'>UPDATE</th>");
			builder.append("<th style='width:10%'>INVOKE</th>");
			builder.append("<th style='width:10%'>RENDER</th>");

			builder.append("</tr>");			
			for(RequestInfo info:events){
				String rTime=requestTimes.get(index);
				builder.append("<tr>");

				builder.append("<td>");
				builder.append("<a href='javascript:void(0)' onclick='updatePhaseGraph(\""+rTime+"\");'>");
				builder.append(info.getRequestPath());
				builder.append("</a>");
				builder.append("</td>");
				for(EventInfo eventInfo:info.getEvents()){
					PhaseEventInfo ev=(PhaseEventInfo) eventInfo;
					String color="green";
					if(ev.isPhaseSkipped()){
						color="red";
					}
					builder.append("<td style='color:"+color+"'>");
					builder.append(ev.getElapsedTime()+" ms");
					builder.append("</td>");
				}								
				builder.append("</tr>");				

				index++;
			}
			builder.append("</table>");


		}
		return builder.toString();
	}

	public String refreshPhaseGraph(ArrayList<String> params){
		System.out.println("Handling Request = "+params);


		// Defining lines
		final int NUM_POINTS = 25;
		final double[] competition = new double[NUM_POINTS];
		final double[] mywebsite = new double[NUM_POINTS];
		for (int i = 0; i < NUM_POINTS; i++) {
			competition[i] = 100-(Math.cos(30*i*Math.PI/180)*10 + 50)*i/20;
			mywebsite[i] = (Math.cos(30*i*Math.PI/180)*10 + 50)*i/20;
		}


		// RESTORE_VIEW, APPLY_REQUEST_VALUES,PROCESS_VALIDATIONS, UPDATE_MODEL_VALUES, INVOKE_APPLICATION, RENDER_RESPONSE 

		List<Long> RESTORE_VIEW_data=new ArrayList<Long>();
		List<Long> APPLY_REQUEST_VALUES_data=new ArrayList<Long>();
		List<Long> PROCESS_VALIDATIONS_data=new ArrayList<Long>();
		List<Long> UPDATE_MODEL_VALUES_data=new ArrayList<Long>();
		List<Long> INVOKE_APPLICATION_data=new ArrayList<Long>();
		List<Long> RENDER_RESPONSE_data=new ArrayList<Long>();


		List<RequestInfo> events=phaseTracker.getEvents(100);

		double maxTime=0;
		for(RequestInfo info:events){
			List<String> times=new ArrayList<String>();	
			for(EventInfo ev:info.getEvents()){
				PhaseEventInfo pev=(PhaseEventInfo)ev;
				times.add(""+ev.getElapsedTime());

				if(ev.getElapsedTime()>maxTime){
					maxTime=ev.getElapsedTime();
				}
				if(pev.getPhaseId().equals(PhaseId.RESTORE_VIEW)){
					RESTORE_VIEW_data.add(pev.getElapsedTime());
				}else if(pev.getPhaseId().equals(PhaseId.APPLY_REQUEST_VALUES)){
					APPLY_REQUEST_VALUES_data.add(pev.getElapsedTime());
				}else if(pev.getPhaseId().equals(PhaseId.PROCESS_VALIDATIONS)){
					PROCESS_VALIDATIONS_data.add(pev.getElapsedTime());
				}	else if(pev.getPhaseId().equals(PhaseId.UPDATE_MODEL_VALUES)){
					UPDATE_MODEL_VALUES_data.add(pev.getElapsedTime());
				}else if(pev.getPhaseId().equals(PhaseId.INVOKE_APPLICATION)){
					INVOKE_APPLICATION_data.add(pev.getElapsedTime());
				}	else if(pev.getPhaseId().equals(PhaseId.RENDER_RESPONSE)){
					RENDER_RESPONSE_data.add(pev.getElapsedTime());
				}
			}
		}
		Line RESTORE_VIEW_Line = Plots.newLine(Data.newData(RESTORE_VIEW_data),  Color.newColor("FF0000"), "RESTORE_VIEW");
		Line APPLY_REQUEST_VALUES_Line = Plots.newLine(Data.newData(APPLY_REQUEST_VALUES_data),  Color.newColor("BFB58E"), "APPLY_REQUEST_VALUES");
		Line PROCESS_VALIDATIONS_Line = Plots.newLine(Data.newData(PROCESS_VALIDATIONS_data),  Color.newColor("328CFA"), "PROCESS_VALIDATIONS");
		Line UPDATE_MODEL_VALUES_Line = Plots.newLine(Data.newData(UPDATE_MODEL_VALUES_data),  Color.newColor("FAD543"), "UPDATE_MODEL_VALUES");
		Line INVOKE_APPLICATION_Line = Plots.newLine(Data.newData(INVOKE_APPLICATION_data),  Color.newColor("CCCCCC"), "INVOKE_APPLICATION");
		Line RENDER_RESPONSE_Line = Plots.newLine(Data.newData(RENDER_RESPONSE_data),  Color.newColor("614A06"), "RENDER_RESPONSE");
		/*
		 	Line pline = Plots.newLine(Data.newData(competition),  Color.newColor("FF0000"), PhaseId.PROCESS_VALIDATIONS.toString());
			plotLines.add(pline);
		 */
		LineChart chart = GCharts.newLineChart(
				RESTORE_VIEW_Line, 
				APPLY_REQUEST_VALUES_Line, 
				PROCESS_VALIDATIONS_Line,
				UPDATE_MODEL_VALUES_Line,
				INVOKE_APPLICATION_Line,
				RENDER_RESPONSE_Line
		);

		chart.setSize(600, 300);
		chart.setTitle("Phase execution time over request count");

		// Adding axis info to chart.
		AxisLabels yAxis = AxisLabelsFactory.newNumericRangeAxisLabels(0, maxTime);
		AxisLabels yAxis2 = AxisLabelsFactory.newAxisLabels("(ms)", 50.0);        
		AxisStyle axisStyle = AxisStyle.newAxisStyle(Color.BLACK, 12, AxisTextAlignment.CENTER);
		yAxis2.setAxisStyle(axisStyle);

		chart.addYAxisLabels(yAxis);
		chart.addYAxisLabels(yAxis2);        

		AxisLabels xAxis = AxisLabelsFactory.newNumericRangeAxisLabels(0, events.size());
		AxisLabels xAxis2 = AxisLabelsFactory.newAxisLabels("Requests", 50.0);        
		chart.addXAxisLabels(xAxis);
		chart.addXAxisLabels(xAxis2);        


		String url = chart.toURLString();
		// EXAMPLE CODE END. Use this url string in your web or
		// Internet application.
		return url;
	}

	public String handleRequest(ArrayList<String> params){
		System.out.println("Handling Request = "+params);

		return "Handle request";
	}



	public String dump(ArrayList<String> params){
		String expression=null;
		if(params.size()!=0){
			expression=params.get(0);
		}

		Object obj=FacesUtil.getValueFromELExpression(expression);

		//FacesContext facesContext=FacesContext.getCurrentInstance();		
		String response="";
		InputStream stream =null;	
		JSFDumpContext context = null;
		try {
			System.out.println("****** CHECKING 2*** "+obj);
			//Application aa = facesContext.getApplication();
			//aa.getPropertyResolver().get

			XMLVisitor visitor = new XMLVisitor();
			context=visitor.getContext();	
			//Only serialize properties that are displayed by the 
			//context.setFilterStrategy(new ELResolverPropertyFilter());	
			context.setReflectionProviderStrategy(new ELResolverReflectionProvider());
			visitor.visit( obj );


			AbstractRenderer renderer=new TreeRenderer();
			renderer.render(visitor.getContext());			
			/*
			 @TODO:Add this back in
			String template="/templates/template.txt";
			stream = this.getClass().getResourceAsStream(template);

			int result=-1;
			StringBuffer sb=new StringBuffer();
			while((result=stream.read())!=-1){
				sb.append((char)result);
			}
			String tempCode=sb.toString();
			 */

			String 	tempCode="<div  class=\"jsfdump\" >%output%</div>";
			tempCode = tempCode.replaceAll("%output%", renderer.getBufferToString());	
			response=tempCode;

		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			if(stream != null){
				try {
					stream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			context.destroy();
		}
		return response;
	}

	/**
	 * 
	 * @param params p1=ScopeTypes separated by ',' example: implicit,request,session
	 * @return
	 */
	public String dumpResolvableProperties(ArrayList<String> params){
		System.out.println("dumpResolvableProperties : "+params.size());
		List<String> responseList = new  ArrayList<String>();
		if(params.size()!=0){
			String[] scopeParams=params.get(0).split(",");
			for(String scope:scopeParams){
				ScopeType scopeType=ScopeType.fromValue(scope);
				responseList.addAll(FacesUtil.getRegisteredBeans(scopeType));
			}
		}

		return responseList.toString().replace("[", "").replace("]", ""); 
	}


	public String dumpObjectProperties(ArrayList<String> params){		
		String expression=null;
		if(params.size()!=0){
			expression=params.get(0);
		}
		Object obj=FacesUtil.getValueFromELExpression(expression);
		//ELResolver resolver= new ImplicitObjectELResolver();
		//ELResolver resolver= facesContext.getELContext().getELResolver();
		/*
		     ArrayELResolver, BeanELResolver, CompositeELResolver, ImplicitObjectELResolver, ListELResolver, MapELResolver, ResourceBundleELResolver, ScopedAttributeELResolver
		 */
		return getObjectProperties(obj).toString().replace("[", "").replace("]", "");
	}

	/**
	 * Get object properties that are resolvable via the common EL Providers
	 * @param obj
	 * @return
	 */
	private List<String> getObjectProperties(Object obj){
		CompositeELResolver  resolver=new CompositeELResolver();
		resolver.add(new BeanELResolver());
		resolver.add(new ArrayELResolver());
		//resolver.add(new ImplicitObjectELResolver());
		resolver.add(new ListELResolver());
		resolver.add(new MapELResolver());
		//resolver.add(new ResourceBundleELResolver());
		//resolver.add(new ScopedAttributeELResolver());

		return FacesUtil.getProperties(resolver, obj);
	}

}
